// Copyright 2022 The Parca Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {useCallback} from 'react';

import {Icon} from '@iconify/react';
import cx from 'classnames';

import {Button, Input, Select, type SelectItem} from '@parca/components';
import {TEST_IDS, testId} from '@parca/test-utils';

import {useProfileViewContext} from '../../context/ProfileViewContext';
import {getPresetByKey, getPresetsForProfileType, isPresetKey} from './filterPresets';
import {useProfileFilters, type ProfileFilter} from './useProfileFilters';

export const isFilterComplete = (filter: ProfileFilter): boolean => {
  // For preset filters, only need type and value
  if (filter.type != null && isPresetKey(filter.type)) {
    return filter.value !== '' && filter.type != null;
  }
  // For regular filters, need all fields
  return (
    filter.value !== '' && filter.type != null && filter.field != null && filter.matchType != null
  );
};

const getFilterTypeItems = (currentProfileType?: string): SelectItem[] => [
  {
    key: 'stack',
    element: {
      active: <>Stack Filter</>,
      expanded: (
        <>
          <span>Stack Filter</span>
          <br />
          <span className="text-xs">Filters entire call stacks</span>
        </>
      ),
    },
  },
  {
    key: 'frame',
    element: {
      active: <>Frame Filter</>,
      expanded: (
        <>
          <span>Frame Filter</span>
          <br />
          <span className="text-xs">Filters individual frames</span>
        </>
      ),
    },
  },
  ...getPresetsForProfileType(currentProfileType).map(preset => ({
    key: preset.key,
    element: {
      active: <>{preset.name}</>,
      expanded: (
        <>
          <span>{preset.name}</span>
          <br />
          <span className="text-xs">{preset.description}</span>
        </>
      ),
    },
  })),
];

const fieldItems: SelectItem[] = [
  {
    key: 'function_name',
    element: {
      active: <>Function</>,
      expanded: <>Function Name</>,
    },
  },
  {
    key: 'binary',
    element: {
      active: <>Binary</>,
      expanded: <>Binary/Executable Name</>,
    },
  },
  {
    key: 'system_name',
    element: {
      active: <>System Name</>,
      expanded: <>System Name</>,
    },
  },
  {
    key: 'filename',
    element: {
      active: <>Filename</>,
      expanded: <>Source Filename</>,
    },
  },
  {
    key: 'address',
    element: {
      active: <>Address</>,
      expanded: <>Memory Address</>,
    },
  },
  {
    key: 'line_number',
    element: {
      active: <>Line Number</>,
      expanded: <>Source Line Number</>,
    },
  },
];

const stringMatchTypeItems: SelectItem[] = [
  {
    key: 'equal',
    element: {
      active: <>Equals</>,
      expanded: <>Equals</>,
    },
  },
  {
    key: 'not_equal',
    element: {
      active: <>Not Equals</>,
      expanded: <>Not Equals</>,
    },
  },
  {
    key: 'contains',
    element: {
      active: <>Contains</>,
      expanded: <>Contains</>,
    },
  },
  {
    key: 'not_contains',
    element: {
      active: <>Not Contains</>,
      expanded: <>Not Contains</>,
    },
  },
  {
    key: 'starts_with',
    element: {
      active: <>Starts With</>,
      expanded: <>Starts With</>,
    },
  },
  {
    key: 'not_starts_with',
    element: {
      active: <>Not Starts With</>,
      expanded: <>Not Starts With</>,
    },
  },
];

const numberMatchTypeItems: SelectItem[] = [
  {
    key: 'equal',
    element: {
      active: <>Equals</>,
      expanded: <>Equals</>,
    },
  },
  {
    key: 'not_equal',
    element: {
      active: <>Not Equals</>,
      expanded: <>Not Equals</>,
    },
  },
];

export interface ProfileFiltersProps {
  readOnly?: boolean;
}

const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Element => {
  const {profileSource} = useProfileViewContext();
  const currentProfileType = profileSource?.ProfileType()?.toString();
  const filterTypeItems = getFilterTypeItems(currentProfileType);

  const {
    localFilters,
    appliedFilters,
    hasUnsavedChanges,
    onApplyFilters,
    addFilter,
    removeFilter,
    updateFilter,
    resetFilters,
  } = useProfileFilters();

  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent<HTMLInputElement>) => {
      if (e.key === 'Enter') {
        e.preventDefault();
        if (e.currentTarget.value.trim() === '') {
          return;
        }
        onApplyFilters();
      }
    },
    [onApplyFilters]
  );

  const filtersToRender = localFilters.length > 0 ? localFilters : appliedFilters ?? [];

  return (
    <div className="flex gap-2 w-full items-start" {...testId(TEST_IDS.PROFILE_FILTERS_CONTAINER)}>
      <div className="flex-1 flex flex-wrap gap-2">
        {filtersToRender.map(filter => {
          const isNumberField = filter.field === 'address' || filter.field === 'line_number';
          const matchTypeItems = isNumberField ? numberMatchTypeItems : stringMatchTypeItems;
          const isPresetFilter = filter.type != null && isPresetKey(filter.type);

          return (
            <div key={filter.id} className="flex items-center gap-0">
              <Select
                items={filterTypeItems}
                selectedKey={filter.type}
                placeholder="Select Filter"
                disabled={readOnly}
                {...testId(TEST_IDS.FILTER_TYPE_SELECT)}
                flyoutTestId="filter-type-select-flyout"
                onSelection={key => {
                  // Check if this is a preset selection
                  if (isPresetKey(key)) {
                    const preset = getPresetByKey(key);
                    if (preset != null) {
                      updateFilter(filter.id, {
                        type: preset.key,
                        field: undefined,
                        matchType: undefined,
                        value: preset.name,
                      });
                    }
                  } else {
                    const newType = key as 'stack' | 'frame';

                    // Check if we're converting a preset filter to a regular filter
                    if (filter.type != null && isPresetKey(filter.type)) {
                      updateFilter(filter.id, {
                        type: newType,
                        field: 'function_name',
                        matchType: 'contains',
                        value: '',
                      });
                    } else {
                      updateFilter(filter.id, {
                        type: newType,
                        field: filter.field ?? 'function_name',
                        matchType: filter.matchType ?? 'contains',
                      });
                    }
                  }
                }}
                className={cx(
                  'gap-0 focus:z-50 focus:relative focus:outline-1',
                  readOnly ? '' : 'pr-1',
                  readOnly && isPresetFilter ? 'rounded-md' : 'rounded-l-md rounded-r-none',
                  !readOnly && (isPresetFilter ? 'rounded-r-none border-r-0' : 'rounded-r-none'),
                  readOnly ? 'w-auto' : filter.type != null ? 'border-r-0 w-auto' : 'w-32'
                )}
                hideCaretDropdown={readOnly}
              />

              {filter.type != null && !isPresetFilter && (
                <>
                  <Select
                    items={fieldItems}
                    selectedKey={filter.field ?? ''}
                    disabled={readOnly}
                    {...testId(TEST_IDS.FILTER_FIELD_SELECT)}
                    flyoutTestId="filter-field-select-flyout"
                    onSelection={key => {
                      const newField = key as ProfileFilter['field'];
                      const isNewFieldNumber = newField === 'address' || newField === 'line_number';
                      const isCurrentFieldNumber =
                        filter.field === 'address' || filter.field === 'line_number';

                      if (isNewFieldNumber !== isCurrentFieldNumber) {
                        updateFilter(filter.id, {
                          field: newField,
                          matchType: 'equal',
                        });
                      } else {
                        updateFilter(filter.id, {field: newField});
                      }
                    }}
                    className={cx(
                      'rounded-none border-r-0 w-32 gap-0 focus:z-50 focus:relative focus:outline-1',
                      readOnly ? '' : 'pr-1'
                    )}
                    hideCaretDropdown={readOnly}
                  />

                  <Select
                    items={matchTypeItems}
                    selectedKey={filter.matchType ?? ''}
                    disabled={readOnly}
                    {...testId(TEST_IDS.FILTER_MATCH_TYPE_SELECT)}
                    flyoutTestId="filter-match-type-select-flyout"
                    onSelection={key =>
                      updateFilter(filter.id, {matchType: key as ProfileFilter['matchType']})
                    }
                    className={cx(
                      'rounded-none border-r-0 gap-0 focus:z-50 focus:relative focus:outline-1',
                      readOnly ? '' : 'pr-1'
                    )}
                    hideCaretDropdown={readOnly}
                  />

                  <Input
                    placeholder="Value"
                    value={filter.value}
                    disabled={readOnly}
                    onChange={e => updateFilter(filter.id, {value: e.target.value})}
                    onKeyDown={handleKeyDown}
                    className="rounded-none w-36 text-sm focus:outline-1"
                    {...testId(TEST_IDS.FILTER_VALUE_INPUT)}
                  />
                </>
              )}

              {!readOnly && (
                <Button
                  variant="neutral"
                  {...testId(TEST_IDS.FILTER_REMOVE_BUTTON)}
                  onClick={() => {
                    // If we're displaying local filters and this is the last one, reset everything
                    if (localFilters.length > 0 && localFilters.length === 1) {
                      resetFilters();
                    }
                    // If we're displaying applied filters and this is the last one, reset everything
                    else if (localFilters.length === 0 && filtersToRender.length === 1) {
                      resetFilters();
                    }
                    // Otherwise, just remove this specific filter
                    else {
                      removeFilter(filter.id);
                    }
                  }}
                  className={cx(
                    'h-[38px] p-3',
                    filter.type != null
                      ? 'rounded-none rounded-r-md'
                      : 'rounded-l-none rounded-r-md'
                  )}
                >
                  <Icon icon="mdi:close" className="h-4 w-4" />
                </Button>
              )}
            </div>
          );
        })}

        {!readOnly && localFilters.length > 0 && (
          <Button
            variant="neutral"
            onClick={addFilter}
            className="p-3 h-[38px]"
            {...testId(TEST_IDS.ADD_FILTER_BUTTON)}
          >
            <Icon icon="mdi:filter-plus-outline" className="h-4 w-4" />
          </Button>
        )}

        {!readOnly && localFilters.length === 0 && (appliedFilters?.length ?? 0) === 0 && (
          <Button
            variant="neutral"
            onClick={addFilter}
            className="flex items-center gap-2"
            {...testId(TEST_IDS.ADD_FILTER_BUTTON)}
          >
            <Icon icon="mdi:filter-outline" className="h-4 w-4" />
            <span>Filter</span>
          </Button>
        )}
      </div>

      {!readOnly && localFilters.length > 0 && (
        <Button
          variant="primary"
          onClick={onApplyFilters}
          disabled={!hasUnsavedChanges || !localFilters.some(isFilterComplete)}
          className={cx('flex items-center gap-2 sticky top-0')}
          {...testId(TEST_IDS.APPLY_FILTERS_BUTTON)}
        >
          <span>Apply</span>
        </Button>
      )}
    </div>
  );
};

export default ProfileFilters;
